using System;
using System.Collections.Generic;
using System.Windows.Forms;
using RideMe.AddIns.Interfaces;

namespace RideMe.AddIns.SourceControl.SVN
{
    /// <summary>
    /// This defines an add-in to allow full Subversion integration within RIDE-ME
    /// </summary>
    public class SubversionAddIn : IAddIn
    {
        #region Private Variables

        private ToolStripSeparator separatorMenuItem;
        private ToolStripMenuItem pathMenuItem;
        private ToolStripMenuItem authorMenuItem;
        private ToolStripMenuItem revisionMenuItem;
        private ToolStripMenuItem dateMenuItem;
        private ToolStripMenuItem addMenuItem;
        private ToolStripMenuItem commitMenuItem;
        private ToolStripMenuItem updateMenuItem;
        private Info _projectInfo;
        private string _projectPath;
        private Control _outputWindow;

        #endregion

        #region Events

        /// <summary>
        /// Captures the project closed event so as to ditch any loaded project Subversion information
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ProjectEvents_ProjectClosed(object sender, RideMe.AddIns.Events.ProjectEventSource.FileEventArgs e)
        {
            _projectPath = null;
            _projectInfo = null;
        }

        /// <summary>
        /// Captures the project opened event so as to load the relevant Subversion information
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProjectEvents_ProjectOpened(object sender, RideMe.AddIns.Events.ProjectEventSource.FileEventArgs e)
        {
            _projectPath = e.Path;
            SubversionProcess process = CreateProcess();
            process.InfoReceived += new EventHandler<InfoEventArgs>(process_InfoReceived);
            process.GetInfo(e.Path);
        }

        /// <summary>
        /// Captures the context menu being created as a node is clicked-on, on first-run creating and adding menu items, and always attempting to find Subversion
        /// information about the relevant selected item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void UserInterfaceEvents_ProjectExplorerContextMenuCreated(object sender, RideMe.AddIns.Events.UserInterfaceEventSource.ProjectExplorerContextMenuCreatedEventArgs e)
        {
            //Create menu items
            if (separatorMenuItem == null)
            {
                separatorMenuItem = new ToolStripSeparator();
                e.AddMenuItem(separatorMenuItem);
            }
            if (pathMenuItem == null)
            {
                pathMenuItem = new ToolStripMenuItem();
                e.AddMenuItem(pathMenuItem);
            }
            if (authorMenuItem == null)
            {
                authorMenuItem = new ToolStripMenuItem();
                e.AddMenuItem(authorMenuItem);
            }
            if (revisionMenuItem == null)
            {
                revisionMenuItem = new ToolStripMenuItem();
                e.AddMenuItem(revisionMenuItem);
            }
            if (dateMenuItem == null)
            {
                dateMenuItem = new ToolStripMenuItem();
                e.AddMenuItem(dateMenuItem);
            }
            if (addMenuItem == null)
            {
                addMenuItem = new ToolStripMenuItem();
                addMenuItem.Text = "Add";
                addMenuItem.Click += new EventHandler(addMenuItem_Click);
                e.AddMenuItem(addMenuItem);
            }
            if (commitMenuItem == null)
            {
                commitMenuItem = new ToolStripMenuItem();
                commitMenuItem.Text = "Commit";
                commitMenuItem.Click += new EventHandler(commitMenuItem_Click);
                e.AddMenuItem(commitMenuItem);
            }
            if (updateMenuItem == null)
            {
                updateMenuItem = new ToolStripMenuItem();
                updateMenuItem.Text = "Update";
                updateMenuItem.Click += new EventHandler(updateMenuItem_Click);
                e.AddMenuItem(updateMenuItem);
            }

            //If we haven't loaded any information, hide the controls
            if (_projectInfo == null)
            {
                HideAll();
            }
            else
            {
                //Grab the entry for the specified path
                Entry entry = _projectInfo.Find(e.Path);
                //If we can't find it, hide the info controls and show the add menu item
                if (entry == null || entry.Revision == "0")
                {
                    //Hide all the info controls
                    HideInfo();
                    HideCommit();
                    HideUpdate();
                    //Grab the status for the item - if its already added, then lets show "Commit", otherwise show "Add"
                    SubversionProcess process = CreateProcess();
                    process.StatusReceived += new EventHandler<StatusEventArgs>(process_StatusReceived);
                    process.GetStatus(e.Path);
                    addMenuItem.Visible = true;
                    addMenuItem.Tag = e.Path;
                }
                else
                {
                    //Hide all controls to start with...
                    HideAll();
                    //If we can find it, show it
                    Show(entry);
                }
            }
        }

        void process_StatusReceived(object sender, StatusEventArgs e)
        {
            if (e != null && e.Status != null && e.Status.Targets != null 
                && e.Status.Targets.Count > 0 && e.Status.Targets[0].Entries != null && e.Status.Targets[0].Entries.Count > 0)
            {
                if (e.Status.Targets[0].Entries[0].WcStatus.Item == "added")
                    addMenuItem.Text = "Commit";
                else
                    addMenuItem.Text = "Add";
            }
        }

        /// <summary>
        /// This updates the specified item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void updateMenuItem_Click(object sender, EventArgs e)
        {
            if (updateMenuItem.Tag != null)
            {
                updateMenuItem.Text = "Updating...";
                SubversionProcess process = CreateProcess();
                process.UpdateCompleted += new EventHandler(process_UpdateCompleted);
                process.Update(updateMenuItem.Tag.ToString());
            }
        }

        /// <summary>
        /// This handles the update command completing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void process_UpdateCompleted(object sender, EventArgs e)
        {
            updateMenuItem.Text = "Update";
        }

        /// <summary>
        /// This commits the specified item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void commitMenuItem_Click(object sender, EventArgs e)
        {
            if (commitMenuItem.Tag != null)
            {
                commitMenuItem.Text = "Committing...";
                SubversionProcess process = CreateProcess();
                process.CommitCompleted += new EventHandler(process_CommitCompleted);
                process.Commit(commitMenuItem.Tag.ToString(), string.Format("UPDATING {0}", commitMenuItem.Tag.ToString())); //TODO: pop-up a dialog for the message
            }
        }

        /// <summary>
        /// This handles the commit command completing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void process_CommitCompleted(object sender, EventArgs e)
        {
            commitMenuItem.Text = "Commit";
            ReloadInfo();
        }

        /// <summary>
        /// This adds the specified item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void addMenuItem_Click(object sender, EventArgs e)
        {
            if (addMenuItem.Tag != null)
            {
                if (addMenuItem.Text == "Add")
                {
                    addMenuItem.Text = "Adding...";
                    SubversionProcess process = CreateProcess();
                    process.AddCompleted += new EventHandler(process_AddCompleted);
                    process.Add(addMenuItem.Tag.ToString());
                }
                else if (addMenuItem.Text == "Commit")
                {
                    addMenuItem.Text = "Committing...";
                    SubversionProcess process = CreateProcess();
                    process.CommitCompleted += new EventHandler(process_CommitCompleted);
                    process.Commit(addMenuItem.Tag.ToString(), string.Format("ADDING: {0}", addMenuItem.Tag.ToString()));
                }
            }
        }

        /// <summary>
        /// This handles the add command completing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void process_AddCompleted(object sender, EventArgs e)
        {
            addMenuItem.Text = "Add";
            ReloadInfo();
        }

        /// <summary>
        /// This handles received information back from the Subversion process
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void process_InfoReceived(object sender, InfoEventArgs e)
        {
            _projectInfo = e.Info;
        }

        /// <summary>
        /// This handles errors from the Subversion process
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void process_ErrorReceived(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (_outputWindow != null)
            {
                if (_outputWindow.InvokeRequired)
                {
                    _outputWindow.Invoke(new MethodInvoker(
                        delegate
                        {
                            process_ErrorReceived(sender, e);
                        }));
                }
                else
                {
                    //This is a kludge to get around the fact that we can't have a reference to the presentation project
                    //Maybe controls should be stored in a separate lib?
                    _outputWindow.GetType().GetMethod("AppendOutput").Invoke(
                        _outputWindow,
                        new object[] { string.Format("[ERROR: Subversion] : {0}", e.Data) });
                }
            }
        }

        /// <summary>
        /// This captures the output window control for use later
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void UserInterfaceEvents_OutputWindowControlCreated(object sender, RideMe.AddIns.Events.UserInterfaceEventSource.ControlCreatedEventArgs e)
        {
            _outputWindow = e.Control;
        }

        #endregion

        #region Methods

        /// <summary>
        /// This creates a process, setting up the common error handling event handler
        /// </summary>
        /// <returns></returns>
        private SubversionProcess CreateProcess()
        {
            SubversionProcess process = new SubversionProcess();
            process.ErrorReceived += new EventHandler<System.Diagnostics.DataReceivedEventArgs>(process_ErrorReceived);
            return process;
        }

        /// <summary>
        /// This reloads the project information
        /// </summary>
        private void ReloadInfo()
        {
            //Reload the project info
            SubversionProcess process = CreateProcess();
            process.InfoReceived += new EventHandler<InfoEventArgs>(process_InfoReceived);
            process.GetInfo(_projectPath);
        }

        /// <summary>
        /// This hides all menu items
        /// </summary>
        private void HideAll()
        {
            HideInfo();
            HideAdd();
            HideCommit();
            HideUpdate();
        }

        /// <summary>
        /// This hides the information menu items
        /// </summary>
        private void HideInfo()
        {
            separatorMenuItem.Visible = false;
            pathMenuItem.Visible = false;
            authorMenuItem.Visible = false;
            revisionMenuItem.Visible = false;
            dateMenuItem.Visible = false;
        }

        /// <summary>
        /// This hides the add menu item
        /// </summary>
        private void HideAdd()
        {
            addMenuItem.Visible = false;
        }

        /// <summary>
        /// This hides the commit menu item
        /// </summary>
        private void HideCommit()
        {
            commitMenuItem.Visible = false;
        }

        /// <summary>
        /// This hides the update menu item
        /// </summary>
        private void HideUpdate()
        {
            updateMenuItem.Visible = false;
        }

        /// <summary>
        /// This shows the specified entry using context menus
        /// </summary>
        /// <param name="entry"></param>
        private void Show(Entry entry)
        {
            separatorMenuItem.Visible = true;
            pathMenuItem.Visible = true;
            authorMenuItem.Visible = true;
            revisionMenuItem.Visible = true;
            dateMenuItem.Visible = true;
            commitMenuItem.Visible = true;
            updateMenuItem.Visible = true;

            pathMenuItem.Text = string.Format("Path: {0}", entry.Path);
            authorMenuItem.Text = string.Format("Author: {0}", entry.Commit.Author);
            revisionMenuItem.Text = string.Format("Revision: {0}", entry.Commit.Revision);
            dateMenuItem.Text = string.Format("Date: {0}", entry.Commit.Date);
            commitMenuItem.Text = "Commit";
            commitMenuItem.Tag = entry.Path;
            updateMenuItem.Text = "Update";
            updateMenuItem.Tag = entry.Path;
        }

        #endregion

        #region IAddIn Members

        /// <summary>
        /// This returns the name of the add-in
        /// </summary>
        public string Name
        {
            get { return "Subversion Integration"; }
        }

        /// <summary>
        /// This returns the author of the add-in
        /// </summary>
        public string Author
        {
            get { return "El Draper <el@eldiablo.co.uk>"; }
        }

        /// <summary>
        /// This returns the description of the add-in
        /// </summary>
        public string Description
        {
            get { return "This provides Subversion integration within the IDE"; }
        }

        /// <summary>
        /// This initialises the add-in
        /// </summary>
        /// <param name="context"></param>
        public void Init(IApplicationContext context)
        {
            context.ProjectEvents.ProjectOpened += new EventHandler<RideMe.AddIns.Events.ProjectEventSource.FileEventArgs>(ProjectEvents_ProjectOpened);
            context.ProjectEvents.ProjectClosed += new EventHandler<RideMe.AddIns.Events.ProjectEventSource.FileEventArgs>(ProjectEvents_ProjectClosed);
            context.UserInterfaceEvents.ProjectExplorerContextMenuCreated += 
                new EventHandler<RideMe.AddIns.Events.UserInterfaceEventSource.ProjectExplorerContextMenuCreatedEventArgs>(UserInterfaceEvents_ProjectExplorerContextMenuCreated);
            context.UserInterfaceEvents.OutputWindowControlCreated += 
                new EventHandler<RideMe.AddIns.Events.UserInterfaceEventSource.ControlCreatedEventArgs>(UserInterfaceEvents_OutputWindowControlCreated);
        }

        /// <summary>
        /// This clears up any resources associated with the add-in
        /// </summary>
        public void Destroy()
        {
            separatorMenuItem = null;
            pathMenuItem = null;
            authorMenuItem = null;
            revisionMenuItem = null;
            dateMenuItem = null;
            addMenuItem = null;
            commitMenuItem = null;
            updateMenuItem = null;
            _projectInfo = null;
            _projectPath = null;
        }

        #endregion
    }
}
